package me.luraframework.gateway.auth;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.luraframework.commons.exception.AppException;
import me.luraframework.commons.utils.JsonUtils;
import me.luraframework.gateway.auth.handler.AuthHandler;
import me.luraframework.gateway.auth.handler.AuthType;
import me.luraframework.gateway.config.ApplicationProperties;
import me.luraframework.gateway.module.route.GwApi;
import me.luraframework.gateway.module.route.GwApiRepository;
import org.apache.logging.log4j.util.Strings;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Map;

import static com.google.common.collect.ImmutableMap.of;
import static me.luraframework.gateway.exception.GatewayErrorCode.UNAUTHORIZED;

@Slf4j
@Component
@RequiredArgsConstructor
public class TokenFilter implements GlobalFilter, Ordered {

    private final ApplicationProperties applicationProperties;
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    private final boolean debug = false;

    private final List<AuthHandler> authHandlers;

    private final GwApiRepository gwApiRepository;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);

        if (debug || isWhitelist(exchange)) {
            return chain.filter(exchange);
        }
        return doAuthentication(exchange, route)
                .then(chain.filter(exchange));
    }

    private boolean isWhitelist(ServerWebExchange exchange) {
        String path = exchange.getRequest().getPath().value();
        String method = exchange.getRequest().getMethodValue();
        for (ApplicationProperties.Whitelist whitelist : applicationProperties.getWhitelist()) {
            if (method.equalsIgnoreCase(whitelist.getMethod()) && antPathMatcher.match(whitelist.getUrl(), path)) {
                return true;
            }
        }
        return false;
    }

    private Mono<Map<String, Object>> doAuthentication(ServerWebExchange exchange, Route route) {
        AuthType authType = (AuthType) route.getMetadata().get(GwApi.ROUTE_METADATA_AUTH_TYPE);
        if (authType == null || authType == AuthType.NONE) {
            return Mono.empty();
        }
        return Flux.fromIterable(authHandlers)
                .filter(authHandler -> authHandler.getAuthType() == authType)
                .last()
                .flatMap(authHandler -> {
                    String token = authHandler.getToken(exchange);
                    if (Strings.isBlank(token)) {
                        return Mono.error(new AppException(UNAUTHORIZED, of()));
                    }

                    return authHandler.getUserContext(exchange)
                            .doOnNext(userInfo -> exchange.getRequest().mutate().header("userInfo", JsonUtils.toStr(userInfo)).build())
                            .switchIfEmpty(Mono.error(new AppException(UNAUTHORIZED, of())));
                });


    }


    @Override
    public int getOrder() {
        return -1;
    }
}
